using System;
using System.Net;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using LuaInterface;

namespace Harrier
{
    public delegate void DebugEventHandler(Object sender, DebugEventArgs e);

    public class DebugEventArgs : EventArgs
    {
        private string s;

        public DebugEventArgs(string s)
        {
            this.s = s;
        }

        public string Text
        {
            get {return s;}
        }
    }

    class Server
    {
        private TcpListener tcpListener;
        private Thread listenThread;
        public event DebugEventHandler debugCallback;
        public Dictionary<int,Client> clients;
        private Lua lua;
        string appPath, binPath, libPath, scriptPath;

        public Server()
        {
            this.Init();
        }

        public Server(DebugEventHandler d)
        {
            this.debugCallback += d;
            this.Init();
        }

        private void Init()
        {
            clients = new Dictionary<int,Client>();
            binPath = System.IO.Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]);
            appPath = Path.GetFullPath(binPath + @"\..");
            libPath = appPath + @"\lib";
            scriptPath = libPath+@"\scripts";
            this.lua = new Lua();
            Output("Harrier Server");
            Debug("Initializing");
            Debug("Registering External Functions");
            lua.RegisterFunction("Debug", this, this.GetType().GetMethod("Debug"));
            lua.RegisterFunction("Output", this, this.GetType().GetMethod("Output"));
            //lua["Server"] = this;
            Debug("Running Init Script");
            RunLua("init.lua");
            Debug("Loading Script Library");
            string[] scriptLibrary = Directory.GetFiles(scriptPath + @"\class\", "*.lua", SearchOption.AllDirectories );
            foreach (string str in scriptLibrary) 
            {
                string scr = str.Replace(scriptPath+@"\","");
                RunLua(scr);
            }
        }

        public void Debug(string s)
        {
            DebugEventArgs e = new DebugEventArgs(" - "+s);
            OnDebug(e);
        }

        public void Output(string s)
        {
            DebugEventArgs e = new DebugEventArgs(s);
            OnDebug(e);
        }

        protected void OnDebug(DebugEventArgs e)
        {
            if (debugCallback != null)
               debugCallback(this, e);
        }

        private void RunLua(string script)
        {
            if (File.Exists(scriptPath + @"\" + script))
            {
                Debug("Loading script "+script);
                lua.DoFile(scriptPath + @"\" + script);
            }
            else
            {
                Debug("Script not found: " + script);
            }
        }

        public void Start(int port)
        {
            this.tcpListener = new TcpListener(IPAddress.Any, port);
            Output("Listening on Port " + port.ToString());
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.Start();
            RunLua("start.lua");
        }

        public void Command(string s)
        {
            Output("");
            string[] para = s.Split(new Char[] {' '},  2);
            string cmd = para[0];
            string parameter = "";
            if (para.Length > 1)
                parameter = para[1];
            switch (cmd.ToLower())
            {
                case "help":
                    Help(parameter);
                    break;
                case "exec":
                    try
                    {
                        lua.DoString(parameter);
                    }
                    catch (LuaException e)
                    {
                        Debug(e.Message);
                    }
                    break;
                case "script":
                    //todo - run a lua script by filename
                    break;
                case "disconnect":
                    //todo - disconnect a connection by ID
                    break;
                case "connlist":
                    //display a list of connections
                    Output("Connections:");
                    Output("------------");
                    Output("");
                    lock (this)
                    {
                        foreach (var client in clients)
                        {
                            Client cli = client.Value;
                            Output(String.Format(":{0,8:N0} : {1}",cli.id,cli.tcpClient.Client.RemoteEndPoint));
                        }
                    }
                    Output("");
                    Output(clients.Count.ToString()+" connections total");
                    Output("");
                    break;
                case "exit":
                case "shutdown":
                case "quit":
                    this.listenThread.Abort();
                    System.Environment.Exit(0);
                    break;
            }
        }

        public void Help(string helpfile)
        {
            if (helpfile=="")
                helpfile="help";
            if (File.Exists(libPath + @"\help\" + helpfile + ".txt"))
            {
                StreamReader streamReader = new StreamReader(libPath + @"\help\" + helpfile + ".txt");
                string text = streamReader.ReadToEnd();
                streamReader.Close();
                Output(text);
            }
            else
            {
                Output("Invalid help option");
            }
        }

        private void ListenForClients()
        {
            this.tcpListener.Start();

            while (true)
            {
                //blocks until a client has connected to the server
                Harrier.Client client = new Harrier.Client();
                client.tcpClient = this.tcpListener.AcceptTcpClient();
                lock (this)
                {
                    this.clients.Add(client.id,client);
                }

                //create a thread to handle communication
                //with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                clientThread.Start(client);
            }
        }

        private void HandleClientComm(object client)
        {
            TcpClient tcpClient = (client as Client).tcpClient;
            int clientID = (client as Client).id;
            NetworkStream clientStream = tcpClient.GetStream();
            //BufferedStream bufferedStream = new BufferedStream(clientStream);
            StreamReader streamReader = new StreamReader(clientStream);
            StreamWriter streamWriter = new StreamWriter(clientStream);
            Boolean connected = true;

            Debug(String.Format("Client {0} Connected", clientID));
            //todo - the following line is just an example, it'll need removing later
            streamWriter.WriteLine(String.Format("Hello Client {0}!", clientID));
            streamWriter.Flush();

            while (connected)
            {
                try
                {
                    string currentLine = streamReader.ReadLine();
                    Console.WriteLine(currentLine);
                    //disconnect if end of stream
                    if (streamReader.EndOfStream)
                        connected = false;
                }
                catch
                {
                    //error, break the connection
                    //break;
                    connected = false;
                }
            }

            Debug("Client Disconnected");
            lock (this)
            {
                this.clients.Remove(clientID);
            }
            tcpClient.Close();
        }
    }


}